using System;
using System.Collections;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using Tycho.Grammar;
using Tycho.Parser.Peg;

namespace Tycho.UnitTests.Grammar {
    [TestFixture]
    public class DynamicChoiceCompilerSpecification {
        [Test]
        public void ShouldCompileChoiceWithNamedProduction () {
            var idCompiler = new IdentifierCompiler ();

            GrammarTree grammarTree = CompileGrammar (idCompiler, "choice {name = id}");

            Assert.That (grammarTree.SubGrammars.ContainsKey ("name"));
            Assert.That (grammarTree.SubGrammars ["name"], Is.SameAs (idCompiler.CompiledIdentifiers["id"]));
        }

        [Test]
        public void ShouldNotCompileAnythingUntilDeferredCompilationsAreRun () {
            var compiler = new IdentifierCompiler ();
            GrammarTree grammarTree = CompileGrammar (compiler, "choice {name = id, int}", false);

            Assert.False (compiler.CompiledIdentifiers.ContainsKey ("id"));
            Assert.False (compiler.CompiledIdentifiers.ContainsKey ("int"));

            Assert.That (grammarTree.SubGrammars.Count, Is.EqualTo (0));
        }

        [Test]
        public void ShouldRewriteProductionsForAssociativity () {
            var choiceAssociativityRewriter = new Mock<IChoiceAssociativityRewriter> ();
            IEnumerable<IProduction> rewrittenProductions = new List<IProduction> {
                                                                                      new Mock<IProduction> ().Object,
                                                                                      new Mock<IProduction> ().Object
                                                                                  };

            choiceAssociativityRewriter
                .Setup (c => c.RewriteForAssciativity (It.IsAny<IEnumerable<IProduction>> (), It.IsAny<IProduction> ()))
                .Returns (rewrittenProductions);

            var identifierCompiler = new IdentifierCompiler ();
            GrammarTree grammarTree = CompileGrammar (identifierCompiler, "choice {name = id, int}", choiceAssociativityRewriter.Object);

            Assert.That (grammarTree.Production, Is.InstanceOf (typeof (ChoiceProduction)));
            var choiceProduction = (ChoiceProduction) grammarTree.Production;
            Assert.That (choiceProduction.Productions, Is.EqualTo (rewrittenProductions));
        }

        private GrammarTree CompileGrammar (IProductionCompiler idCompiler, string source) {
            return CompileGrammar (idCompiler, source, true, new Mock<IChoiceAssociativityRewriter> ().Object);
        }

        private GrammarTree CompileGrammar (IProductionCompiler idCompiler, string source, bool runDeferredCompilations) {
            return CompileGrammar (idCompiler, source, runDeferredCompilations, new Mock<IChoiceAssociativityRewriter> ().Object);
        }

        private GrammarTree CompileGrammar (IProductionCompiler idCompiler, string source, IChoiceAssociativityRewriter associativityRewriter) {
            return CompileGrammar (idCompiler, source, true, associativityRewriter);
        }

        private GrammarTree CompileGrammar (IProductionCompiler compiler, string source, bool runDeferredCompilations, IChoiceAssociativityRewriter associativityRewriter) {
            IProduction choiceParser = GrammarLoader.CreateProduction ();

            ITerm term = choiceParser.ParseTerm (source);
            var context = GetContext ();
            var deferredCompilations = context.DeferredCompilations;

            var dynamicChoiceCompiler = new DynamicChoiceCompiler (compiler, associativityRewriter);
            GrammarTree grammarTree = dynamicChoiceCompiler.Compile (term, null, context);

            if (runDeferredCompilations) {
                deferredCompilations.RunCompilationSteps ();
            }

            return grammarTree;
        }

        private static ProductionCompilerContext GetContext () {
            return new ProductionCompilerContext (
                new Mock<IProductionCompilerEnvironment> ().Object,
                new Mock<IProductionLookup> ().Object,
                new Mock<IDynamicGrammar> ().Object,
                new DeferredCompilations ());
        }

        class IdentifierCompiler : IProductionCompiler {
            public readonly Dictionary<string, GrammarTree> CompiledIdentifiers = new Dictionary<string, GrammarTree> ();

            public GrammarTree Compile (ITerm term, ChoicePrecedence precedence, ProductionCompilerContext context) {
                if (term is IdentifierTerm) {
                    var grammarTree = new GrammarTree (new Mock<IProduction> ().Object);
                    CompiledIdentifiers.Add (((IdentifierTerm) term).Name, grammarTree);
                    return grammarTree;
                } else {
                    Assert.Fail ("didn't expect anything other than an identifier");
                    return null;
                }
            }
        }
    }
}